#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <mutex>
#include "Util.hpp"

namespace ns_index
{
    class DocInfo // 解析后的html文档的相关信息
    {
    public:
        std::string _title;
        std::string _content;
        std::string _url;
        uint64_t _doc_id;
    };
    class InvertedElem
    {
    public:
        uint64_t _doc_id;
        std::string _word;
        int _weight; // 关键词word在该文档内的权重,方便后续查找时按顺序显示
    };


    class Index
    {
    public:
        typedef std::vector<InvertedElem> InvertedList; // 得到倒排拉链,是一组数据
    private:
        Index() {}
        ~Index() {}
        Index(const Index &index) = delete;
        Index &operator=(const Index &index) = delete;

        static Index *_instance;
        static std::mutex _mutex;

    public:
        static Index* GetInstance()
        {
            if (_instance == nullptr)
            {
                _mutex.lock();
                if (_instance == nullptr)
                {
                    _instance = new Index();
                }
                _mutex.unlock();
            }
            return _instance;
        }

    private:
        // 正排索引 根据文档id找到文档相关信息,采用数组下标直接映射
        std::vector<DocInfo> _forward_index;
        // 倒排索引 根据关键字word找到一个或一组文档id
        std::unordered_map<std::string, InvertedList> _inverted_index;

    private:
        DocInfo *BuildForwardIndex(const std::string &html_line)
        {
            // 1~ 切分字符串
            std::vector<std::string> ret_strs;
            const std::string sep = "\3";
            ns_util::StringUtil::SplitString(html_line, &ret_strs, sep);
            if (ret_strs.size() < 3)
                return nullptr;

            // 2~ 填充doc_info
            DocInfo doc_info;
            doc_info._title = ret_strs[0];
            doc_info._content = ret_strs[1];
            doc_info._url = ret_strs[2];
            doc_info._doc_id = _forward_index.size(); // 插入第一个时id== size ==0

            // 3~ 插入到正排索引_forward_index
            _forward_index.push_back(std::move(doc_info));
            return &_forward_index.back();
        }
        bool BuildInvertedIndex(const DocInfo &doc_info)
        {
            struct words_cnt
            {
                int title_cnt = 0;
                int content_title = 0;
            };
            // 1~ 对doc_info的title和content进行分词
            std::unordered_map<std::string, words_cnt> words_frequency;
            std::vector<std::string> words_title;//保存title分词后的结果
            std::vector<std::string> words_content;//保存content分词后的结果
            ns_util::JiebaUtil::CutString(doc_info._title, &words_title);
            ns_util::JiebaUtil::CutString(doc_info._content, &words_content);
            // 2~ 统计词频填充words_frequency
            for (auto &word : words_title)//to_lower转换不能是const修饰
            {
                boost::to_lower(word); // 需要统一转化成为小写,因为搜索时不区分大小写
                //boost::to_lower_copy(word);
                words_frequency[word].title_cnt++;
            }
            for (auto &word : words_content)
            {
                boost::to_lower(word); // 需要统一转化成为小写,因为搜索时不区分大小写
                //boost::to_lower_copy(word);
                words_frequency[word].content_title++;
            }

            // 3~ 自定义权重 title:content = 10:1
            static const int title_weight = 10;
            static const int content_weight = 1;
            // 4~ 对words_frequency内的每个关键词创建InvertedElem并填充
            for (const auto &kv : words_frequency)
            {
                InvertedElem inverted_ele;
                inverted_ele._doc_id = doc_info._doc_id;
                inverted_ele._word = kv.first;
                inverted_ele._weight =
                    title_weight * kv.second.title_cnt +
                    content_weight * kv.second.content_title;

            // 5~ 将该文档的所有InvertedElem分别插入到倒排索引 _inverted_index中
            InvertedList &inverted_list = _inverted_index[kv.first];
            inverted_list.push_back(std::move(inverted_ele));
                //_inverted_index[kv.first].push_back(std::move(inverted_ele));
            }

            return true;
        }

    public:
        bool BuildIndex(const std::string &input_path) // 构建索引
        {
            std::fstream in(input_path, std::ios::in | std::ios::binary);
            if (!in.is_open())
            {
                std::cerr << "BuildIndex fail! " << input_path << " cannot open" << std::endl;
                return false;
            }

            std::string html_line; // 每个html的的DocInfo以\n间隔
            int cnt=1; //debug
            while (std::getline(in, html_line))
            {
                DocInfo *doc_info = BuildForwardIndex(html_line);
                if (doc_info == nullptr)
                {
                    std::cerr << "BuildForwardIndex fail! "
                              << "who? " << html_line << "  continue next html" << std::endl;
                    continue;
                }
                if (!BuildInvertedIndex(*doc_info))
                {
                    std::cerr << "BuildInvertedIndex fail! "
                              << "id: " << doc_info->_doc_id << std::endl;
                    continue;
                }
                ++cnt;
                if(cnt%100 == 0)
                std::cout<<"cnt:"<<cnt<<std::endl; 
            }
            std::cout<<"BuildIndex over cnt:"<<cnt<<std::endl;
            in.close();
            return true;
        }
        DocInfo *GetForwardIndex(uint64_t doc_id)
        {
            if (doc_id >= _forward_index.size())
            {
                std::cerr << "doc_id is out of range! " << std::endl;
                return nullptr;
            }
            return &_forward_index[doc_id];
        }
        InvertedList *GetInvertedList(const std::string &word)
        {
            if (!_inverted_index.count(word))
            {
                std::cerr <<"GetInvertedList fail!"<< word << " is not exist!" << std::endl;
                return nullptr;
            }
            return &_inverted_index[word];
        }
    };
    Index *Index::_instance = nullptr;
    std::mutex Index::_mutex;
}